package com.ceadeal.javafxboot.service.impl;

import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.google.common.collect.Lists;
import com.ceadeal.javafxboot.config.ArcSoftConfig;
import com.ceadeal.javafxboot.config.ArcSoftEngineConfig;
import com.ceadeal.javafxboot.config.ArcSoftFunctionConfig;
import com.ceadeal.javafxboot.entity.FaceUserInfo;
import com.ceadeal.javafxboot.factory.FaceEngineFactory;
import com.ceadeal.javafxboot.service.IArcSoftService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @Author 张寒轩
 * @Date 2021/3/20 9:18
 * @Description 虹软人脸识别图片接口
 */
@Slf4j
@Service
public class ArcSoftServiceImpl implements IArcSoftService {

    private ExecutorService executorService;

    private GenericObjectPool<FaceEngine> faceEngineObjectPool;

    @Resource
    private ArcSoftConfig arcSoftConfig;

    @Resource
    private ArcSoftEngineConfig engineConfig;

    @Resource
    private ArcSoftFunctionConfig functionConfig;

    FaceEngine faceEngine = null;
    /**
     * @return void
     * @auther 张寒轩
     * @date 2021/3/19 14:27
     * @description 初始化对象池
     */
    @PostConstruct
    private void init() {
        int threadPoolSize = Runtime.getRuntime().availableProcessors();
        ThreadFactory springThreadFactory =
                new CustomizableThreadFactory("arcSoft-face-");
        /**
         * 核心线程数=CPU核心数
         * 最大线程数=CPU核心数*2
         * 回收线程时间=2
         * 回收线程单位=秒
         * 队列=CPU核心数
         * 线程工厂=自定义工厂(设置线程名)
         * 拒绝策略=直接丢弃
         */
        executorService = new ThreadPoolExecutor(
                threadPoolSize,
                threadPoolSize*2,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(threadPoolSize*2),
                springThreadFactory,
                new ThreadPoolExecutor.AbortPolicy());
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(threadPoolSize);
        poolConfig.setMaxTotal(threadPoolSize);
        poolConfig.setMinIdle(threadPoolSize);
        poolConfig.setLifo(false);
        //引擎配置
        EngineConfiguration engineConfiguration = new EngineConfiguration();
        engineConfiguration.setDetectMode(
                "VIDEO".equals(engineConfig.getDetectMode()) ?
                        DetectMode.ASF_DETECT_MODE_VIDEO :
                        DetectMode.ASF_DETECT_MODE_IMAGE);
        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
        if ("ASF_OP_0_ONLY".equals(engineConfig.getDetectFaceOrientPriority())) {
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);
        } else if ("ASF_OP_90_ONLY".equals(engineConfig.getDetectFaceOrientPriority())) {
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_90_ONLY);
        } else if ("ASF_OP_180_ONLY".equals(engineConfig.getDetectFaceOrientPriority())) {
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_180_ONLY);
        } else if ("ASF_OP_270_ONLY".equals(engineConfig.getDetectFaceOrientPriority())) {
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_270_ONLY);
        } else {
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
        }
        engineConfiguration.setDetectFaceMaxNum(engineConfig.getDetectFaceMaxNum());
        engineConfiguration.setDetectFaceScaleVal(engineConfig.getDetectFaceScale());
        //        //引擎配置
//        EngineConfiguration engineConfiguration = new EngineConfiguration();
//        engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
//        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
//        engineConfiguration.setDetectFaceMaxNum(10);
//        engineConfiguration.setDetectFaceScaleVal(16);
        //功能配置
        FunctionConfiguration functionConfiguration = new FunctionConfiguration();
        functionConfiguration.setSupportAge(functionConfig.isSupportAge());
        functionConfiguration.setSupportFace3dAngle(functionConfig.isSupportFace3dAngle());
        functionConfiguration.setSupportFaceDetect(functionConfig.isSupportFaceDetect());
        functionConfiguration.setSupportFaceRecognition(functionConfig.isSupportFaceRecognition());
        functionConfiguration.setSupportGender(functionConfig.isSupportGender());
        functionConfiguration.setSupportLiveness(functionConfig.isSupportLiveness());
        functionConfiguration.setSupportIRLiveness(functionConfig.isSupportIRLiveness());
        engineConfiguration.setFunctionConfiguration(functionConfiguration);
        FaceEngineFactory factory=new FaceEngineFactory(arcSoftConfig.getLibPath(),
                arcSoftConfig.getAppId(),
                arcSoftConfig.getSdkKey(), engineConfiguration);
        try {
            faceEngine = factory.create();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        //创建对象池
        faceEngineObjectPool = new GenericObjectPool(
                new FaceEngineFactory(arcSoftConfig.getLibPath(),
                        arcSoftConfig.getAppId(),
                        arcSoftConfig.getSdkKey(), engineConfiguration), poolConfig);

    }

    /**
     * @auther 张寒轩
     * @date 2021/3/19 14:53
     * @description 多线程人脸比对
     * @return 匹配到的人脸数组
     */
    private class CompareFaceTask implements Callable<List<FaceUserInfo>> {
        //匹配人脸对象数组
        private List<FaceUserInfo> faceList;
        //查找人脸对象
        private FaceFeature targetFaceFeature;

        public CompareFaceTask(List<FaceUserInfo> faceList, FaceFeature targetFaceFeature) {
            this.faceList = faceList;
            this.targetFaceFeature = targetFaceFeature;
        }
//        java 多 线程 里的 Callable 和 Future 一般是成对出现, 当我们获取 线程 执行结果时, 需要用到他们,
//        Callable 用于产生结果, Future 用于获取结果. Callable 相当于计算产生结果部分, 而 Future 是获取计算产生结果的部分.
//        1. CallableCallable 是一个接口, 只包含call方法, 我们一般实现这个类, 然后在 异步计算 在默认的情况下， 线程 Thread
//        对象不具有返回值的功能，如果在需要取得返回值的情况下是极为不方便的，但在 Java 1.5的并发包中可以使用 Future 和 Callable
//        来使 线程 具有返回值的功能。
//Callable接口类似于Runnable，从名字就可以看出来了，但是Runnable不会返回结果，并且无法抛出返回结果的异常，
// 而Callable功能更强大一些，被线程执行后，可以返回值，这个返回值可以被Future拿到，
        @Override
        public List<FaceUserInfo> call() {
            FaceEngine faceEngine = null;
            //识别到的人脸列表
            List<FaceUserInfo> resultFaceInfoList = Lists.newLinkedList();
            try {
                faceEngine = faceEngineObjectPool.borrowObject();
                for (FaceUserInfo face : faceList) {
                    FaceFeature sourceFaceFeature = new FaceFeature();
                    sourceFaceFeature.setFeatureData(face.getFace_feature());
                    FaceSimilar faceSimilar = new FaceSimilar();
                    faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
                    float score = faceSimilar.getScore();
                    if (score > 0.82) {
                        FaceUserInfo info = FaceUserInfo.builder().id(face.getId())
                                .name(face.getName())
                                .score(score)
                                .face_feature(face.getFace_feature()).build();
                        resultFaceInfoList.add(info);
                    }
                }
            } catch (Exception e) {
                log.error("", e);
            } finally {
                if (faceEngine != null) {
                    faceEngineObjectPool.returnObject(faceEngine);
                }
            }
            return resultFaceInfoList;
        }
    }

    /**
     * @param imageInfo 图片
     * @return List<FaceInfo> 检测到的人脸信息数组
     * @auther 张寒轩
     * @date 2021/3/19 15:57
     * @description 人脸检测
     */
    private List<FaceInfo> detectFaces(ImageInfo imageInfo) {
//        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();
            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<>();
            //人脸检测
            faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            return faceInfoList;
        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                faceEngineObjectPool.returnObject(faceEngine);
            }
        }
        return null;
    }

    /**
     * @param file 图片
     * @return List<FaceInfo> 检测到的人脸信息数组
     * @auther 张寒轩
     * @date 2021/3/19 15:57
     * @description 人脸检测
     */
    @Override
    public List<FaceInfo> detectFaces(File file) {
        return detectFaces(ImageFactory.getGrayData(file));
    }

    /**
     * @param input 图片
     * @return List<FaceInfo> 检测到的人脸信息数组
     * @auther 张寒轩
     * @date 2021/3/19 15:57
     * @description 人脸检测
     */
    @Override
    public List<FaceInfo> detectFaces(InputStream input) {
        return detectFaces(ImageFactory.getGrayData(input));
    }

    /**
     * @param bytes 图片
     * @return List<FaceInfo> 检测到的人脸信息数组
     * @auther 张寒轩
     * @date 2021/3/19 15:57
     * @description 人脸检测
     */
    @Override
    public List<FaceInfo> detectFaces(byte[] bytes) {
        return detectFaces(ImageFactory.getGrayData(bytes));
    }

    /**
     * @param imageInfo 图片
     * @return List<FaceFeature> 返回人脸特征数组
     * @auther 张寒轩
     * @date 2021/3/20 9:41
     * @description 特征提取
     */
    private List<FaceFeature> extractFaceFeature(ImageInfo imageInfo) throws InterruptedException {
        FaceEngine faceEngine = null;
        try {
            List<FaceFeature> faceFeatures = new ArrayList<>();
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();
            List<FaceInfo> faceInfoList = detectFaces(imageInfo);
            if (!CollectionUtils.isEmpty(faceInfoList)) {
                //循环提取人脸数据
                for (FaceInfo faceInfo : faceInfoList) {
                    FaceFeature faceFeature = new FaceFeature();
                    //提取人脸特征
                    faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfo, faceFeature);
                    faceFeatures.add(new FaceFeature(faceFeature.getFeatureData()));
                }
            }
            return faceFeatures;
        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }

        }
        return null;
    }

    /**
     * @param file 图片
     * @return List<FaceFeature> 返回人脸特征数组
     * @auther 张寒轩
     * @date 2021/3/20 9:41
     * @description 特征提取
     */
    @Override
    public List<FaceFeature> extractFaceFeature(File file) throws InterruptedException {
        return extractFaceFeature(ImageFactory.getGrayData(file));
    }

    /**
     * @param input 图片
     * @return List<FaceFeature> 返回人脸特征数组
     * @auther 张寒轩
     * @date 2021/3/20 9:41
     * @description 特征提取
     */
    @Override
    public List<FaceFeature> extractFaceFeature(InputStream input) throws InterruptedException {
        return extractFaceFeature(ImageFactory.getGrayData(input));
    }

    /**
     * @param bytes 图片
     * @return List<FaceFeature> 返回人脸特征数组
     * @auther 张寒轩
     * @date 2021/3/20 9:41
     * @description 特征提取
     */
    @Override
    public List<FaceFeature> extractFaceFeature(byte[] bytes) throws InterruptedException {
        return extractFaceFeature(ImageFactory.getGrayData(bytes));
    }

    /**
     * @param faceFeature   需要查找人脸
     * @param faceUserInfos 人脸库
     * @param size          拆分数组大小
     * @return List<FaceUserInfo> 按相似度从大到小排列的识别到的人脸比较结果列表
     * @auther 张寒轩
     * @date 2021/3/20 9:24
     * @description 多线程人脸查找
     */
    @Override
    public List<FaceUserInfo> compareFaceFeature(FaceFeature faceFeature, List<FaceUserInfo> faceUserInfos, int size) throws InterruptedException, ExecutionException {
        //识别到的人脸列表
        List<FaceUserInfo> resultFaceList = Lists.newLinkedList();
        List<List<FaceUserInfo>> faceParentList = Lists.partition(faceUserInfos, size);//每size个人脸一个线程
        //ExecutorCompletionService 是将 Executor和BlockQueue结合的jdk类，其实现的主要目的是：提交任务线程，每一个线程任务直线完成后，
        // 将返回值放在阻塞队列中，然后可以通过阻塞队列的take ()方法返回 对应线程的执行结果！
        //executorService是一个线程池
        CompletionService<List<FaceUserInfo>> completionService = new ExecutorCompletionService(executorService);
        for (List<FaceUserInfo> faceList : faceParentList) {//启动多个线程进行人脸比较
            completionService.submit(new ArcSoftServiceImpl.CompareFaceTask(faceList, faceFeature));//CompareFaceTask是一个callable类
        }
        for (int i = 0; i < faceParentList.size(); i++) {//取回各个线程的比较结果并合并
            List<FaceUserInfo> faceInfos = completionService.take().get();
            if (!CollectionUtils.isEmpty(faceInfos)) {
                resultFaceList.addAll(faceInfos);
            }
        }
        //相似度从大到小排序
        resultFaceList.sort((h1, h2) -> h2.getScore().compareTo(h1.getScore()));
        return resultFaceList;
    }
}